Imports System.IO
Imports AutoCAD

''' <summary>
''' CAD服务
''' 主要实现以下几个功能:
''' 1. 构建图元数据表;
''' 2. 获取图元对象并添加到图元数据表中;
'''     2.1. 构建基础的图元数据行;
'''     2.2. 添加多段线图元数据;
'''     2.3. 添加圆与圆弧图元数据;
'''     2.4. 添加多线图元数据;
'''     2.5. 添加发现数据
''' 3. 与CAD进行简单交互;
'''     3.1. 缩放到指定图元对象;
'''     3.2. 高亮显示图元对象;
''' </summary>
Public Class CADService2


#Region "构建图层"
    ' 初始化需要的图形

    ''' <summary>
    ''' 为文档初始化图层
    ''' </summary>
    ''' <param name="doc"></param>
    Public Sub InitLayer(ByRef doc As AcadDocument)

        Dim layers As New List(Of String)
        With layers
            .Add("1_板材")
            .Add("1_工件")
            .Add("1_正面孔")
            .Add("1_反面孔")
            .Add("1_水平孔")
            .Add("1_正面槽")
            .Add("1_反面槽")
            .Add("1_拉米诺")
        End With

        ' 重复添加无错误,直接遍历添加图层即可
        For Each item In layers
            doc.Layers.Add(item)
        Next

        ' 添加线性
        ' 状态线形状 acadiso.lin "C:\Users\Admin\AppData\Roaming\Autodesk\AutoCAD 2018\R22.0\chs\Support\acadiso.lin"    
        Dim path As String = $"{doc.Application.Path}\UserDataCache\zh-cn\Support\acadiso.lin"
        Dim linetypes As New List(Of String)
        For Each lty As AcadLineType In doc.Linetypes
            linetypes.Add(lty.Name)
        Next
        If Not linetypes.Contains("DASHED") Then
            doc.Linetypes.Load("DASHED", path)
        End If


        ' 设置颜色
        With doc.Layers.Item("1_工件")
            .color = AcColor.acWhite
            .Lineweight = ACAD_LWEIGHT.acLnWt030
        End With

        With doc.Layers.Item("1_正面孔")
            .color = 140
            .Lineweight = ACAD_LWEIGHT.acLnWt015
        End With

        With doc.Layers.Item("1_反面孔")
            .color = 144
            .Lineweight = ACAD_LWEIGHT.acLnWt015
            .Linetype = "DASHED"
        End With

        With doc.Layers.Item("1_水平孔")
            .color = 20
            .Lineweight = ACAD_LWEIGHT.acLnWt015
            .Linetype = "DASHED"
        End With

        With doc.Layers.Item("1_正面槽")
            .color = 210
            .Lineweight = ACAD_LWEIGHT.acLnWt015
        End With

        With doc.Layers.Item("1_反面槽")
            .color = 214
            .Lineweight = ACAD_LWEIGHT.acLnWt015
            .Linetype = "DASHED"
        End With

        With doc.Layers.Item("1_拉米诺")
            .color = 60
            .Lineweight = ACAD_LWEIGHT.acLnWt015
        End With


    End Sub


#End Region



#Region "获得图元对象数据"

    ''' <summary>
    ''' 构建图元数据表
    ''' </summary>
    ''' <returns></returns>
    Public Function buildEntityDataTable(ByRef tableName As String) As DataTable
        Dim dt As New DataTable(tableName)

        With dt.Columns
            ' 工件信息
            .Add("PartNum") '工件序号
            .Add("PartIndex") '工件索引
            .Add("PartName") '工件名称
            .Add("MaterialType") ' 材种
            .Add("FinishLength") '成料长
            .Add("FinishWidth") '成料宽
            .Add("FinishThickness") '成料厚
            .Add("CutLength") '开料长
            .Add("CutWidth") '开料宽
            .Add("CutThickness") '开料厚
            .Add("EdgeCode") '封边说明 X1;X2;Y1;Y2
            .Add("WorkDesc") '加工说明

            ' 基础
            .Add("PartName") '自定义的零件名称
            .Add("ObjectID")
            .Add("ObjectName")
            .Add("Handle")
            .Add("Layer")
            .Add("Color")
            .Add("Material")
            .Add("LinetypeScale")

            ' 位置
            .Add("PositionX", GetType(Double))
            .Add("PositionY", GetType(Double))
            .Add("PositionZ", GetType(Double))

            ' BBox
            .Add("MinX", GetType(Double))
            .Add("MinY", GetType(Double))
            .Add("MinZ", GetType(Double))
            .Add("MaxX", GetType(Double))
            .Add("MaxY", GetType(Double))
            .Add("MaxZ", GetType(Double))

            ' 法向
            .Add("NormalX", GetType(Double))
            .Add("NormalY", GetType(Double))
            .Add("NormalZ", GetType(Double))

            ' 起始点与结束点
            .Add("StartPointX", GetType(Double))
            .Add("StartPointY", GetType(Double))
            .Add("StartPointZ", GetType(Double))
            .Add("EndPointX", GetType(Double))
            .Add("EndPointY", GetType(Double))
            .Add("EndPointZ", GetType(Double))

            '其他属性
            .Add("Diameter", GetType(Double)) '直径
            .Add("Coordinates") '多段线坐标点集合
            .Add("Bulge") '多段线凸度
            .Add("Elevation", GetType(Double)) '标高, 多段线\圆\圆弧等高度位置
            .Add("ConstantWidth", GetType(Double)) '多段线的全局宽度, 表示水平孔的直径
            .Add("Thickness", GetType(Double)) '厚度
            .Add("EffectiveName") '块引用名称
            .Add("MLineScale", GetType(Double)) '多线比例,用于表示宽度
            .Add("Justification") '多线对正,对应刀具补偿
            .Add("Length", GetType(Double)) ' 多段线长度

        End With
        dt.PrimaryKey = New DataColumn() {dt.Columns("Handle")}

        Return dt
    End Function

    ''' <summary>
    ''' 获取图元数据
    ''' </summary>
    ''' <param name="ss"></param>
    ''' <returns></returns>
    Public Function GetEntityDataTable(ByRef ss As AcadSelectionSet)

        '构建图元数据表
        Dim dt As DataTable = buildEntityDataTable($"{Path.GetFileNameWithoutExtension(ss.Application.ActiveDocument.Name)}_EntityData")

        ' 添加属性
        Dim row As DataRow
        For Each ent As AcadEntity In ss
            row = dt.NewRow
            ' 基础属性
            buildEntityDataRow_Base(row, ent)
            Select Case ent.ObjectName
                Case "AcDb3dSolid" '实体
                    '没有法向
                Case "AcDbXline" '构造线
                    '没有法向
                Case "AcDbSpline" '样条曲线
                    '没有法向

                Case "AcDbBlockReference" '块引用
                    buildEntityDataRow_Normal(row, ent) ' 法向

                Case "AcDbEllipse" '椭圆
                    buildEntityDataRow_Normal(row, ent) ' 法向

                Case "AcDbMline" '多线
                    '没有法向
                    buildEntityDataRow_StartAndEndPoint(row, ent) ' 开始点与结束点

                Case "AcDbPolyline" '多段线
                    buildEntityDataRow_Normal(row, ent) ' 法向
                    buildEntityDataRow_StartAndEndPoint(row, ent) ' 开始点与结束点
                    buildEntityDataRow_PLineData(row, ent) ' 多段线

                Case "AcDbCircle" '圆
                    buildEntityDataRow_Normal(row, ent) ' 法向
                    buildEntityDataRow_Diameter(row, ent) '直径

                Case "AcDbArc" '圆弧
                    buildEntityDataRow_Normal(row, ent) ' 法向
                    buildEntityDataRow_StartAndEndPoint(row, ent) ' 开始点与结束点
                    buildEntityDataRow_Diameter(row, ent) '直径

                Case "AcDbLine" '直线
                    buildEntityDataRow_Normal(row, ent) ' 法向
                    buildEntityDataRow_StartAndEndPoint(row, ent) ' 开始点与结束点

                Case "AcDbPoint" '点
                    buildEntityDataRow_Normal(row, ent) ' 法向

                Case Else

            End Select

            dt.Rows.Add(row)
        Next

        Return dt
    End Function

    ''' <summary>
    ''' 构建实体数据行_基础/位置/BBOX
    ''' </summary>
    ''' <param name="row"></param>
    ''' <param name="ent"></param>
    ''' <returns></returns>
    Private Function buildEntityDataRow_Base(ByRef row As DataRow, ByRef ent As AcadEntity) As DataRow

        Dim v As AcadEntity = ent
        With row
            ' 基础
            .Item("PartName") = v.Layer
            .Item("ObjectName") = v.ObjectName
            .Item("ObjectID") = v.ObjectID
            .Item("Handle") = v.Handle
            .Item("Layer") = v.Layer
            .Item("Color") = v.color
            .Item("Material") = v.Material
            .Item("LinetypeScale") = v.LinetypeScale '比例
            ' BBOX
            Dim minp, maxp : ent.GetBoundingBox(minp, maxp) ' 最小点与最大点
            .Item("MinX") = minp(0)
            .Item("MinY") = minp(1)
            .Item("MinZ") = minp(2)
            .Item("MaxX") = maxp(0)
            .Item("MaxY") = maxp(1)
            .Item("MaxZ") = maxp(2)
            ' 位置
            Select Case v.ObjectName
                Case "AcDbCircle"
                    Dim c As AcadCircle = ent
                    .Item("PositionX") = c.Center(0)
                    .Item("PositionY") = c.Center(1)
                    .Item("PositionZ") = c.Center(2)
                Case "AcDbArc"
                    Dim arc As AcadArc = ent
                    .Item("PositionX") = (arc.StartPoint(0) + arc.EndPoint(0)) / 2
                    .Item("positionY") = (arc.StartPoint(1) + arc.EndPoint(1)) / 2
                    .Item("PositionZ") = (arc.StartPoint(2) + arc.EndPoint(2)) / 2
                Case "AcDbPolyline"
                    Dim pl As AcadLWPolyline = ent
                    .Item("PositionX") = pl.Coordinates(0)
                    .Item("PositionY") = pl.Coordinates(1)
                    .Item("PositionZ") = pl.Elevation
                Case "AcDbMline"
                    Dim ml As AcadMLine = ent
                    .Item("PositionX") = ml.Coordinates(0)
                    .Item("PositionY") = ml.Coordinates(1)
                    .Item("PositionZ") = ml.Coordinates(2)
                Case "AcDbLine"
                    Dim l As AcadLine = ent
                    .Item("PositionX") = l.StartPoint(0)
                    .Item("PositionY") = l.StartPoint(1)
                    .Item("PositionZ") = l.StartPoint(2)
                Case "AcDbBlockReference"
                    Dim blkr As AcadBlockReference = ent
                    .Item("PositionX") = blkr.InsertionPoint(0)
                    .Item("PositionY") = blkr.InsertionPoint(1)
                    .Item("PositionZ") = blkr.InsertionPoint(2)
                Case Else
            End Select
        End With

        Return row

    End Function


    ''' <summary>
    ''' 构建实体数据行_法向(不是所有对象都有法向)
    ''' </summary>
    ''' <param name="row"></param>
    ''' <param name="ent"></param>
    Private Function buildEntityDataRow_Normal(ByRef row As DataRow, ByRef ent As AcadEntity) As DataRow
        Dim v As AcadEntity = ent
        With row
            '法线
            .Item("NormalX") = v.Normal(0)
            .Item("NormalY") = v.Normal(1)
            .Item("NormalZ") = v.Normal(2)
        End With
        Return row
    End Function


    ''' <summary>
    ''' 构建实体数据行_多段线数据
    ''' </summary>
    ''' <param name="row"></param>
    ''' <param name="ent"></param>
    ''' <returns></returns>
    Private Function buildEntityDataRow_PLineData(ByRef row As DataRow, ByRef ent As AcadLWPolyline)

        Dim coors As New List(Of String)
        For Each p As String In ent.Coordinates
            coors.Add(p)
        Next

        Dim bulge As New List(Of String)
        For i = 0 To coors.Count / 2 - 1
            bulge.Add(ent.GetBulge(i))
        Next
        If ent.Closed Then
            bulge.Add(ent.GetBulge(coors.Count / 2 - 1))
        End If

        Dim CoordinatesString As String = String.Join(",", coors.ToArray)
        Dim bulgeString As String = String.Join(",", bulge.ToArray)

        ' 添加点属性
        With row
            .Item("Coordinates") = CoordinatesString
            .Item("Bulge") = bulgeString
            .Item("Elevation") = ent.Elevation ' 标高/高程/海拔
            .Item("Thickness") = ent.Thickness '  厚度
            .Item("LinetypeScale") = ent.LinetypeScale '比例
            .Item("ConstantWidth") = ent.ConstantWidth '全局宽度
            .Item("Length") = ent.Length
        End With

        Return row
    End Function


    ''' <summary>
    ''' 构建实体数据行_多线属性
    ''' </summary>
    ''' <param name="row"></param>
    ''' <param name="ent"></param>
    ''' <returns></returns>
    Private Function buildEntityDataRow_MLineData(ByRef row As DataRow, ByRef ent As AcadMLine) As DataRow
        ' 添加点属性
        With row
            .Item("MLineScale") = ent.MLineScale ' 标高/高程/海拔
            .Item("Justification") = ent.Justification '  厚度
        End With
        Return row
    End Function


    ''' <summary>
    ''' 构建实体数据行_起始点与结束点
    ''' </summary>
    ''' <param name="row"></param>
    ''' <param name="ent"></param>
    ''' <returns></returns>
    Private Function buildEntityDataRow_StartAndEndPoint(ByRef row As DataRow, ByRef ent As AcadEntity)
        With row
            Select Case ent.ObjectName
                Case "AcDbArc"
                    Dim arc As AcadArc = ent
                    .Item("StartPointX") = arc.StartPoint(0)
                    .Item("StartPointY") = arc.StartPoint(1)
                    .Item("StartPointZ") = arc.StartPoint(2)

                    .Item("EndPointX") = arc.EndPoint(0)
                    .Item("EndPointY") = arc.EndPoint(1)
                    .Item("EndPointZ") = arc.EndPoint(2)

                Case "AcDbPolyline"
                    Dim pl As AcadLWPolyline = ent
                    Dim maxIndex As Integer = UBound(pl.Coordinates)
                    .Item("StartPointX") = pl.Coordinates(0)
                    .Item("StartPointY") = pl.Coordinates(1)
                    .Item("StartPointZ") = pl.Elevation

                    .Item("EndPointX") = pl.Coordinates(maxIndex - 1)
                    .Item("EndPointY") = pl.Coordinates(maxIndex)
                    .Item("EndPointZ") = pl.Elevation

                Case "AcDbMline"
                    Dim ml As AcadMLine = ent
                    .Item("StartPointX") = ml.Coordinates(0)
                    .Item("StartPointY") = ml.Coordinates(1)
                    .Item("StartPointZ") = ml.Coordinates(2)

                    .Item("EndPointX") = ml.Coordinates(3)
                    .Item("EndPointY") = ml.Coordinates(4)
                    .Item("EndPointZ") = ml.Coordinates(5)

                Case "AcDbLine"
                    Dim l As AcadLine = ent
                    .Item("StartPointX") = l.StartPoint(0)
                    .Item("StartPointY") = l.StartPoint(1)
                    .Item("StartPointZ") = l.StartPoint(2)

                    .Item("EndPointX") = l.EndPoint(0)
                    .Item("EndPointY") = l.EndPoint(1)
                    .Item("EndPointZ") = l.EndPoint(2)
                Case Else
            End Select
        End With
        Return row
    End Function

    ''' <summary>
    ''' 构建实体数据行_直径
    ''' </summary>
    ''' <param name="row"></param>
    ''' <param name="ent"></param>
    ''' <returns></returns>
    Private Function buildEntityDataRow_Diameter(ByRef row As DataRow, ByRef ent As AcadEntity)
        With row
            .Item("Thickness") = ent.Thickness
            Select Case ent.ObjectName
                Case "AcDbArc"
                    Dim arc As AcadArc = ent
                    .Item("Diameter") = arc.Radius * 2
                    .Item("ConstantWidth") = arc.Lineweight '全局宽度
                Case "AcDbCircle"
                    Dim cir As AcadCircle = ent
                    .Item("Diameter") = cir.Diameter
                Case Else
            End Select
        End With
        Return row
    End Function


#End Region



#Region "交互"

    ''' <summary>
    ''' 获取活动的CAD文档
    ''' </summary>
    ''' <returns></returns>
    Public Function getActiveDoc() As AcadDocument
        Dim cad As AcadApplication = Nothing
        If cad Is Nothing Then
            cad = CADService.GetCadObject
        End If
        If cad Is Nothing Then
            MsgBox("未检测到CAD应用")
            Return Nothing
        End If
        Return cad.ActiveDocument
    End Function

    ''' <summary>
    ''' 获取当前选择集
    ''' </summary>
    ''' <param name="doc"></param>
    ''' <returns></returns>
    Public Function GetPickfirstSelectionSet(ByRef doc As AcadDocument) As AcadSelectionSet
        Return doc.PickfirstSelectionSet
    End Function


    ''' <summary>
    ''' 缩放视图控件,根据图元对象缩放
    ''' </summary>
    ''' <param name="handle"></param>
    Public Sub Zoom_ByHandle(ByRef handle As String)
        Dim doc As AcadDocument = getActiveDoc()
        If doc Is Nothing Then Exit Sub

        Dim ent As AcadEntity = doc.HandleToObject(handle)

        Dim minp, maxp
        ent.GetBoundingBox(minp, maxp)

        Dim x As Double = (maxp(0) - minp(0)) * 0.2
        Dim y As Double = (maxp(1) - minp(1)) * 0.2

        doc.PostCommand($"Z {minp(0) - x / 2},{minp(1) - y / 2}  {maxp(0) + x / 2},{maxp(1) + y / 2} ")
        doc.Application.Update()
    End Sub



    ''' <summary>
    ''' 高亮显示图元对象
    ''' </summary>
    ''' <param name="handle"></param>
    Public Sub HighlightEntity_ByHandle(ByRef handle As String)
        Dim doc As AcadDocument = getActiveDoc()
        If doc Is Nothing Then Exit Sub

        For Each e As AcadEntity In doc.ModelSpace
            e.Highlight(False)
        Next
        Dim ent As AcadEntity = doc.HandleToObject(handle)
        ent.Highlight(True)
        doc.Application.Update()
    End Sub

#End Region


#Region "计算"

#End Region


#Region "计算.两点间距"

    ''' <summary>
    ''' 计算两坐标点距离
    ''' </summary>
    ''' <param name="sx"></param>
    ''' <param name="sy"></param>
    ''' <param name="ex"></param>
    ''' <param name="ey"></param>
    ''' <returns></returns>
    Public Shared Function GetDistance(sx As Double, sy As Double, ex As Double, ey As Double) As Double
        Return Math.Sqrt((ex - sx) ^ 2 + (ey - sy) ^ 2)
    End Function

    ''' <summary>
    ''' 计算两坐标点距离
    ''' </summary>
    ''' <param name="sx"></param>
    ''' <param name="sy"></param>
    ''' <param name="sz"></param>
    ''' <param name="ex"></param>
    ''' <param name="ey"></param>
    ''' <param name="ez"></param>
    ''' <returns></returns>
    Public Shared Function GetDistance(sx As Double, sy As Double, sz As Double, ex As Double, ey As Double, ez As Double) As Double
        Return Math.Sqrt((ex - sx) ^ 2 + (ey - sy) ^ 2 + (ez - sz) ^ 2)
    End Function


#End Region


End Class
